/*
 * Scala (https://www.scala-lang.org)
 *
 * Copyright EPFL and Lightbend, Inc. dba Akka
 *
 * Licensed under Apache License 2.0
 * (http://www.apache.org/licenses/LICENSE-2.0).
 *
 * See the NOTICE file distributed with this work for
 * additional information regarding copyright ownership.
 */

package scala
package tools.nsc
package doc
package model

import base.comment._
import diagram._

import java.net.URI
import java.nio.file.Paths

import scala.collection.mutable, mutable.ListBuffer
import scala.reflect.io._
import scala.reflect.macros.internal.macroImpl
import scala.util.matching.Regex.quoteReplacement

import model.{RootPackage => RootPackageEntity}
import symtab.Flags

/** This trait extracts all required information for documentation from compilation units */
class ModelFactory(val global: Global, val settings: doc.Settings) {
  thisFactory: ModelFactory
               with ModelFactoryImplicitSupport
               with ModelFactoryTypeSupport
               with DiagramFactory
               with CommentFactory
               with TreeFactory
               with MemberLookup =>

  import global._
  import definitions.{ ObjectClass, NothingClass, AnyClass, AnyValClass, AnyRefClass, AnnotationClass }
  import rootMirror.{ RootPackage, EmptyPackage }
  import ModelFactory._

  def templatesCount = docTemplatesCache.count(_._2.isDocTemplate) - droppedPackages.size

  private var _modelFinished = false
  def modelFinished: Boolean = _modelFinished
  private var universe: Universe = null

  def makeModel: Option[Universe] = {
    val universe = new Universe { thisUniverse =>
      thisFactory.universe = thisUniverse
      val settings = thisFactory.settings
      val rootPackage = modelCreation.createRootPackage
    }
    _modelFinished = true
    // complete the links between model entities, everything that couldn't have been done before
    universe.rootPackage.completeModel()

    Some(universe) filter (_.rootPackage != null)
  }

  // state:
  var ids = 0
  private val droppedPackages = mutable.Set[PackageImpl]()
  protected val docTemplatesCache = new mutable.LinkedHashMap[Symbol, DocTemplateImpl]
  protected val noDocTemplatesCache = new mutable.LinkedHashMap[Symbol, NoDocTemplateImpl]
  def packageDropped(tpl: DocTemplateImpl) = tpl match {
    case p: PackageImpl => droppedPackages(p)
    case _ => false
  }

  // This unsightly hack closes issue scala/bug#4086.
  private lazy val modifiedSynchronized: Symbol = {
    val sym = definitions.Object_synchronized
    val info = (sym.info: @unchecked) match {
      case PolyType(ts, MethodType(List(bp), mt)) =>
        val cp = bp.cloneSymbol.setPos(bp.pos).setInfo(definitions.byNameType(bp.info))
        PolyType(ts, MethodType(List(cp), mt))
    }
    sym.cloneSymbol.setPos(sym.pos).setInfo(info)
  }

  def optimize(str: String): String =
    if (str.length < 16) str.intern else str

  /* ============== IMPLEMENTATION PROVIDING ENTITY TYPES ============== */

  abstract class EntityImpl(val sym: Symbol, val inTpl: TemplateImpl) extends Entity {
    val name = optimize(sym.nameString)
    val universe = thisFactory.universe

    // Debugging:
    // assert(id != 36, sym + "  " + sym.getClass)
    //println("Creating entity #" + id + " [" + kind + " " + qualifiedName + "] for sym " + sym.kindString + " " + sym.ownerChain.reverse.map(_.name).mkString("."))

    def inTemplate: TemplateImpl = inTpl
    def toRoot: List[EntityImpl] = this :: inTpl.toRoot
    def qualifiedName = name
    def annotations = sym.annotations.filterNot(_.atp =:= typeOf[macroImpl]).map(makeAnnotation)
    def inPackageObject: Boolean = sym.owner.isModuleClass && sym.owner.sourceModule.isPackageObject
    def isType = sym.name.isTypeName
  }

  trait TemplateImpl extends EntityImpl with TemplateEntity {
    override def qualifiedName: String =
      if (inTemplate == null || inTemplate.isRootPackage) name else optimize(inTemplate.qualifiedName + "." + name)
    def isPackage = sym.hasPackageFlag
    def isTrait = sym.isTrait
    def isClass = sym.isClass && !sym.isTrait
    def isObject = sym.isModule && !sym.hasPackageFlag
    def isCase = sym.isCase
    def isRootPackage = false
    def selfType = if (sym.thisSym eq sym) None else Some(makeType(sym.thisSym.typeOfThis, this))
  }

  abstract class MemberImpl(sym: Symbol, inTpl: DocTemplateImpl) extends EntityImpl(sym, inTpl) with MemberEntity {
    // If the current tpl is a DocTemplate, we consider itself as the root for resolving link targets (instead of the
    // package the class is in) -- so people can refer to methods directly [[foo]], instead of using [[MyClass.foo]]
    // in the doc comment of MyClass
    def linkTarget: DocTemplateImpl = inTpl

    // if there is a field symbol, the ValDef will use it, which means docs attached to it will be under the field symbol, not the getter's
    protected[this] def commentCarryingSymbol(sym: Symbol) =
      if (sym == modifiedSynchronized) definitions.Object_synchronized
      else if (sym.hasAccessorFlag && sym.accessed.exists) sym.accessed
      else sym

    lazy val comment = thisFactory.comment(commentCarryingSymbol(sym), linkTarget, inTpl)

    def group = comment flatMap (_.group) getOrElse defaultGroup
    override def inTemplate = inTpl
    override def toRoot: List[MemberImpl] = this :: inTpl.toRoot
    def inDefinitionTemplates =
        if (inTpl == null)
          docTemplatesCache(RootPackage) :: Nil
        else
          makeTemplate(sym.owner)::(sym.allOverriddenSymbols map { inhSym => makeTemplate(inhSym.owner) })
    def visibility = {
      if (sym.isPrivateLocal) PrivateInInstance()
      else if (sym.isProtectedLocal) ProtectedInInstance()
      else {
        val qual =
          if (sym.hasAccessBoundary) {
            val qualTpl = makeTemplate(sym.privateWithin)
            if (qualTpl != inTpl) Some(qualTpl)
            else None
          } else None
        def tp(c: TemplateImpl) = makeType(c.sym.tpe, inTpl)
        if (sym.isPrivate) PrivateInTemplate(None)
        else if (sym.isProtected) ProtectedInTemplate(qual.map(tp))
        else qual match {
          case Some(q) => PrivateInTemplate(Some(tp(q)))
          case None => Public()
        }
      }
    }
    def flags = {
      val fgs = ListBuffer.empty[Paragraph]
      if (sym.isImplicit) fgs += Paragraph(Text("implicit"))
      if (sym.isSealed) fgs += Paragraph(Text("sealed"))
      if (!sym.isTrait && (sym hasFlag Flags.ABSTRACT)) fgs += Paragraph(Text("abstract"))
      /* Resetting the DEFERRED flag is a little trick here for refined types: (example from scala.collections)
       * {{{
       *     implicit def traversable2ops[T](t: scala.collection.GenTraversableOnce[T]) = new TraversableOps[T] {
       *       def isParallel = ...
       * }}}
       * the type the method returns is TraversableOps, which has all-abstract symbols. But in reality, it couldn't have
       * any abstract terms, otherwise it would fail compilation. So we reset the DEFERRED flag. */
      if (!sym.isTrait && (sym hasFlag Flags.DEFERRED) && (!isImplicitlyInherited)) fgs += Paragraph(Text("abstract"))
      if (!sym.isModule && (sym hasFlag Flags.FINAL)) fgs += Paragraph(Text("final"))
      if (sym.isMacro) fgs += Paragraph(Text("macro"))
      fgs.toList
    }
    def deprecation =
      if (sym.isDeprecated)
        Some((sym.deprecationMessage, sym.deprecationVersion) match {
          case (Some(msg), Some(ver)) => parseWiki("''(Since version " + ver + ")'' " + msg, NoPosition, inTpl)
          case (Some(msg), None) => parseWiki(msg, NoPosition, inTpl)
          case (None, Some(ver)) =>  parseWiki("''(Since version " + ver + ")''", NoPosition, inTpl)
          case (None, None) => Body(Nil)
        })
      else
        comment flatMap { _.deprecated }
    def migration =
      if(sym.hasMigrationAnnotation)
        Some((sym.migrationMessage, sym.migrationVersion) match {
          case (Some(msg), Some(ver)) => parseWiki("''(Changed in version " + ver + ")'' " + msg, NoPosition, inTpl)
          case (Some(msg), None) => parseWiki(msg, NoPosition, inTpl)
          case (None, Some(ver)) =>  parseWiki("''(Changed in version " + ver + ")''", NoPosition, inTpl)
          case (None, None) => Body(Nil)
        })
      else
        None

    def resultType = {
      def resultTpe(tpe: Type): Type = tpe match { // similar to finalResultType, except that it leaves singleton types alone
        case PolyType(_, res) => resultTpe(res)
        case MethodType(_, res) => resultTpe(res)
        case NullaryMethodType(res) => resultTpe(res)
        case _ => tpe
      }
      val tpe = byConversion.fold(sym.tpe) (_.toType memberInfo sym)
      makeTypeInTemplateContext(resultTpe(tpe), inTemplate, sym)
    }
    def isDef = false
    def isVal = false
    def isLazyVal = false
    def isVar = false
    def isConstructor = false
    def isAliasType = false
    def isAbstractType = false
    def isAbstract =
      // for the explanation of conversion == null see comment on flags
      ((!sym.isTrait && ((sym hasFlag Flags.ABSTRACT) || (sym hasFlag Flags.DEFERRED)) && (!isImplicitlyInherited)) ||
      sym.isAbstractClass || sym.isAbstractType) && !sym.isSynthetic

    def signature = externalSignature(sym)
    lazy val signatureCompat = {

      def defParams(mbr: Any): String = mbr match {
        case d: MemberEntity with Def =>
          val paramLists: List[String] =
            if (d.valueParams.isEmpty) Nil
            else d.valueParams.map(ps => ps.map(_.resultType.name).mkString("(",",",")"))
          paramLists.mkString
        case _ => ""
      }

      def tParams(mbr: Any): String = mbr match {
        case hk: HigherKinded if hk.typeParams.nonEmpty =>
          def boundsToString(hi: Option[TypeEntity], lo: Option[TypeEntity]): String = {
            def bound0(bnd: Option[TypeEntity], pre: String): String = bnd match {
              case None => ""
              case Some(tpe) => pre ++ tpe.toString
            }
            bound0(hi, "<:") ++ bound0(lo, ">:")
          }
          "[" + hk.typeParams.map(tp => tp.variance + tp.name + tParams(tp) + boundsToString(tp.hi, tp.lo)).mkString(", ") + "]"
        case _ => ""
      }

      (name + tParams(this) + defParams(this) +":"+ resultType.name).replaceAll("\\s","") // no spaces allowed, they break links
    }
    // these only apply for NonTemplateMemberEntities
    def useCaseOf: Option[MemberImpl] = None
    def byConversion: Option[ImplicitConversionImpl] = None
    def isImplicitlyInherited = false
    def isShadowedImplicit    = false
    def isAmbiguousImplicit   = false
    def isShadowedOrAmbiguousImplicit = false
  }

  /** A template that is not documented at all. The class is instantiated during lookups, to indicate that the class
   *  exists, but should not be documented (either it's not included in the source or it's not visible)
   */
  class NoDocTemplateImpl(sym: Symbol, inTpl: TemplateImpl) extends EntityImpl(sym, inTpl) with TemplateImpl with HigherKindedImpl with NoDocTemplate {
    assert(modelFinished, this)
    assert(!(noDocTemplatesCache isDefinedAt sym), (sym, noDocTemplatesCache(sym)))
    noDocTemplatesCache += (sym -> this)
    def isDocTemplate = false
  }

  /** An inherited template that was not documented in its original owner - example:
   *  in classpath:  trait T { class C } -- T (and implicitly C) are not documented
   *  in the source: trait U extends T -- C appears in U as a MemberTemplateImpl -- that is, U has a member for it
   *  but C doesn't get its own page
   */
  abstract class MemberTemplateImpl(sym: Symbol, inTpl: DocTemplateImpl) extends MemberImpl(sym, inTpl) with TemplateImpl with HigherKindedImpl with MemberTemplateEntity {
    // no templates cache for this class, each owner gets its own instance
    def isDocTemplate = false
    lazy val definitionName = optimize(inDefinitionTemplates.head.qualifiedName + "." + name)
    def valueParams: List[List[ValueParam]] = Nil /** TODO, these are now only computed for DocTemplates */

    def parentTypes =
      if (sym.hasPackageFlag || sym == AnyClass) List() else {
        val tps = (this match {
          case a: AliasType => sym.tpe.dealias.parents
          case a: AbstractType => sym.info.bounds match {
            case TypeBounds(lo, RefinedType(parents, decls)) => parents
            case TypeBounds(lo, hi) => hi :: Nil
            case _ => Nil
          }
          case _ => sym.tpe.parents
        }) map { _.asSeenFrom(sym.thisType, sym) }
        makeParentTypes(RefinedType(tps, EmptyScope), Some(this), inTpl)
      }
  }

   /** The instantiation of `TemplateImpl` triggers the creation of the following entities:
    *  All ancestors of the template and all non-package members.
    */
  abstract class DocTemplateImpl(sym: Symbol, inTpl: DocTemplateImpl) extends MemberTemplateImpl(sym, inTpl) with DocTemplateEntity {
    assert(!modelFinished, (sym, inTpl))
    assert(!(docTemplatesCache isDefinedAt sym), sym)
    docTemplatesCache += (sym -> this)

    if (settings.verbose.value)
      inform("Creating doc template for " + sym)

    override def linkTarget: DocTemplateImpl = this
    override def toRoot: List[DocTemplateImpl] = this :: inTpl.toRoot

    protected def reprSymbol: Symbol = sym

    def inSource = {
      val sourceFile = reprSymbol.sourceFile
      if (sourceFile != null && !reprSymbol.isSynthetic)
        Some((sourceFile, reprSymbol.pos.line))
      else
        None
    }

    def sourceUrl = {

      if (!settings.docsourceurl.isDefault)
        inSource.map { case (file, line) =>
          val filePathExt = {
            // file path is relative to source root (-sourcepath); use an absolute path otherwise
            val sp = settings.sourcepath.value
            val fileUri = file.file.toPath.toUri
            if (sp.isEmpty) fileUri.getRawPath
            else Paths.get(sp).toUri.relativize(fileUri).getRawPath
          }
          val (filePath, fileExt) =
            filePathExt.lastIndexOf('.') match {
              case -1 => (filePathExt, "")
              case i  => filePathExt.splitAt(i)
            }
          val tplOwner = this.inTemplate.qualifiedName
          val tplName = this.name
          val patchedString = expandUrl(settings.docsourceurl.value, filePath, fileExt, filePathExt, line, tplOwner, tplName)
          new URI(patchedString).toURL
        }
      else None
    }

    private def templateAndType(ancestor: Symbol): (TemplateImpl, TypeEntity) = (makeTemplate(ancestor), makeType(reprSymbol.info.baseType(ancestor), this))
    lazy val (linearizationTemplates, linearizationTypes) =
      (reprSymbol.ancestors map templateAndType).unzip

    /* Subclass cache */
    private lazy val subClassesCache = (
      if (sym == AnyRefClass || sym == AnyClass) null
      else ListBuffer[DocTemplateEntity]()
    )
    def registerSubClass(sc: DocTemplateEntity): Unit = {
      if (subClassesCache != null)
        subClassesCache += sc
    }
    def directSubClasses = if (subClassesCache == null) Nil else subClassesCache.toList

    /* Implicitly convertible class cache */
    private var implicitlyConvertibleClassesCache: ListBuffer[(DocTemplateImpl, ImplicitConversionImpl)] = null
    def registerImplicitlyConvertibleClass(dtpl: DocTemplateImpl, conv: ImplicitConversionImpl): Unit = {
      if (implicitlyConvertibleClassesCache == null)
        implicitlyConvertibleClassesCache = ListBuffer[(DocTemplateImpl, ImplicitConversionImpl)]()
      implicitlyConvertibleClassesCache += ((dtpl, conv))
    }

    def incomingImplicitlyConvertedClasses: List[(DocTemplateImpl, ImplicitConversionImpl)] =
      if (implicitlyConvertibleClassesCache == null)
        List()
      else
        implicitlyConvertibleClassesCache.toList

    // the implicit conversions are generated lazily, on completeModel
    lazy val conversions: List[ImplicitConversionImpl] =
      if (settings.docImplicits.value) makeImplicitConversions(sym, this) else Nil

    // members as given by the compiler
    lazy val memberSyms      = sym.info.members.filter(s => membersShouldDocument(s, this)).toList

    // the inherited templates (classes, traits or objects)
    val memberSymsLazy  = memberSyms.filter(t => templateShouldDocument(t, this) && !inOriginalOwner(t, this))
    // the direct members (methods, values, vars, types and directly contained templates)
    val memberSymsEager = memberSyms.filter(!memberSymsLazy.contains(_))
    // the members generated by the symbols in memberSymsEager
    val ownMembers      = (memberSymsEager.flatMap(makeMember(_, None, this)))

    // all the members that are documented PLUS the members inherited by implicit conversions
    var members: List[MemberImpl] = ownMembers

    def templates       = members collect { case c: TemplateEntity with MemberEntity => c }
    def methods         = members collect { case d: Def => d }
    def values          = members collect { case v: Val => v }
    def abstractTypes   = members collect { case t: AbstractType => t }
    def aliasTypes      = members collect { case t: AliasType => t }

    /**
     * This is the final point in the core model creation: no DocTemplates are created after the model has finished, but
     * inherited templates and implicit members are added to the members at this point.
     */
    def completeModel(): Unit = {
      // DFS completion
      // since alias types and abstract types have no own members, there's no reason for them to call completeModel
      if (!sym.isAliasType && !sym.isAbstractType)
        for (member <- members)
          member match {
            case d: DocTemplateImpl => d.completeModel()
            case _ =>
          }

      members :::= memberSymsLazy.map(modelCreation.createLazyTemplateMember(_, this))

      outgoingImplicitlyConvertedClasses

      for (pt <- sym.info.parents; parentTemplate <- findTemplateMaybe(pt.typeSymbol)) parentTemplate registerSubClass this

      // the members generated by the symbols in memberSymsEager PLUS the members from the usecases
      val allMembers = ownMembers ::: ownMembers.flatMap(_.useCaseOf).distinct
      implicitsShadowing = makeShadowingTable(allMembers, conversions, this)
      // finally, add the members generated by implicit conversions
      members :::= conversions.flatMap(_.memberImpls)
    }

    var implicitsShadowing = Map[MemberEntity, ImplicitMemberShadowing]()

    lazy val outgoingImplicitlyConvertedClasses: List[(TemplateEntity, TypeEntity, ImplicitConversionImpl)] =
      conversions flatMap (conv =>
        if (!implicitExcluded(conv.conversionQualifiedName))
          conv.targetTypeComponents map {
            case (template, tpe) =>
              template match {
                case d: DocTemplateImpl if (d != this) => d.registerImplicitlyConvertibleClass(this, conv)
                case _ => // nothing
              }
              (template, tpe, conv)
          }
        else List()
      )

    override def isDocTemplate = true
    private[this] lazy val companionSymbol =
      if (sym.isAliasType || sym.isAbstractType) {
        inTpl.sym.info.member(sym.name.toTermName) match {
          case NoSymbol => NoSymbol
          case s =>
            s.info match {
              case ot: OverloadedType =>
                NoSymbol
              case _ =>
                // that's to navigate from val Foo: FooExtractor to FooExtractor :)
                s.info.resultType.typeSymbol
            }
        }
      }
      else
        sym.companionSymbol

    def companion =
      companionSymbol match {
        case NoSymbol => None
        case comSym if !isEmptyJavaObject(comSym) && (comSym.isClass || comSym.isModule) =>
          makeTemplate(comSym) match {
            case d: DocTemplateImpl => Some(d)
            case _ => None
          }
        case _ => None
      }

    def constructors: List[MemberImpl with Constructor] = if (isClass) members collect { case d: Constructor => d } else Nil
    def primaryConstructor: Option[MemberImpl with Constructor] = if (isClass) constructors find { _.isPrimary } else None
    override def valueParams =
      // we don't want params on a class (non case class) signature
      if (isCase) primaryConstructor match {
        case Some(const) => const.sym.paramss map (_ map (makeValueParam(_, this)))
        case None => List()
      }
      else List.empty

    // These are generated on-demand, make sure you don't call them more than once
    def inheritanceDiagram = makeInheritanceDiagram(this)
    def contentDiagram = makeContentDiagram(this)

    def groupSearch[T](extractor: Comment => Option[T]): Option[T] = {
      val comments = comment +: linearizationTemplates.collect { case dtpl: DocTemplateImpl => dtpl.comment }
      comments.flatten.map(extractor).flatten.headOption orElse {
        Option(inTpl) flatMap (_.groupSearch(extractor))
      }
    }

    def groupDescription(group: String): Option[Body] = groupSearch(_.groupDesc.get(group)) orElse { if (group == defaultGroup) defaultGroupDesc else None }
    def groupPriority(group: String): Int = groupSearch(_.groupPrio.get(group)) getOrElse { if (group == defaultGroup) defaultGroupPriority else 0 }
    def groupName(group: String): String = groupSearch(_.groupNames.get(group)) getOrElse { if (group == defaultGroup) defaultGroupName else group }
  }

  abstract class PackageImpl(sym: Symbol, inTpl: PackageImpl) extends DocTemplateImpl(sym, inTpl) with Package {
    override def inTemplate = inTpl
    override def toRoot: List[PackageImpl] = this :: inTpl.toRoot
    override def reprSymbol = sym.info.members.find (_.isPackageObject) getOrElse sym

    def packages = members collect { case p: PackageImpl if !(droppedPackages contains p) => p }
  }

  abstract class RootPackageImpl(sym: Symbol) extends PackageImpl(sym, null) with RootPackageEntity

  abstract class NonTemplateMemberImpl(sym: Symbol, conversion: Option[ImplicitConversionImpl],
                                       override val useCaseOf: Option[MemberImpl], inTpl: DocTemplateImpl)
           extends MemberImpl(sym, inTpl) with NonTemplateMemberEntity {
    override lazy val comment = {
      def nonRootTemplate(sym: Symbol): Option[DocTemplateImpl] =
        if (sym eq RootPackage) None else findTemplateMaybe(sym)

      val inRealTpl = conversion match {
        case Some(conv) =>
          /* Variable precedence order for implicitly added members: Take the variable definitions from ...
           * 1. the target of the implicit conversion
           * 2. the definition template (owner)
           * 3. the current template
           */
          nonRootTemplate(conv.toType.typeSymbol).orElse(nonRootTemplate(sym.owner)).getOrElse(inTpl)
        case None =>
          // This case handles members which were inherited but not implemented or overridden
          inTpl
      }

      thisFactory.comment(commentCarryingSymbol(sym), inRealTpl, inRealTpl)
    }

    override def inDefinitionTemplates = useCaseOf.fold(super.inDefinitionTemplates)(_.inDefinitionTemplates)

    override def qualifiedName = optimize(inTemplate.qualifiedName + "#" + name)
    lazy val definitionName = {
      val qualifiedName = conversion.fold(inDefinitionTemplates.head.qualifiedName)(_.conversionQualifiedName)
      optimize(qualifiedName + "#" + name)
    }
    def isUseCase = useCaseOf.isDefined
    override def byConversion: Option[ImplicitConversionImpl] = conversion
    override def isImplicitlyInherited = { assert(modelFinished, "cannot check if implicitly inherited before model is finished"); conversion.isDefined }
    override def isShadowedImplicit    = isImplicitlyInherited && inTpl.implicitsShadowing.get(this).map(_.isShadowed).getOrElse(false)
    override def isAmbiguousImplicit   = isImplicitlyInherited && inTpl.implicitsShadowing.get(this).map(_.isAmbiguous).getOrElse(false)
    override def isShadowedOrAmbiguousImplicit = isShadowedImplicit || isAmbiguousImplicit
  }

  abstract class NonTemplateParamMemberImpl(sym: Symbol, conversion: Option[ImplicitConversionImpl],
                                            useCaseOf: Option[MemberImpl], inTpl: DocTemplateImpl)
           extends NonTemplateMemberImpl(sym, conversion, useCaseOf, inTpl) {
    def valueParams = {
      val info = conversion.fold(sym.info)(_.toType memberInfo sym)
      info.paramss map { ps => (ps.zipWithIndex) map { case (p, i) =>
        if (p.nameString contains "$") makeValueParam(p, inTpl, optimize("arg" + i)) else makeValueParam(p, inTpl)
      }}
    }
  }

  abstract class ParameterImpl(val sym: Symbol, val inTpl: TemplateImpl) extends ParameterEntity {
    val name = optimize(sym.nameString)
  }

  private trait AliasImpl {
    def sym: Symbol
    def inTpl: TemplateImpl
    def alias = makeTypeInTemplateContext(sym.tpe.dealias, inTpl, sym)
  }

  private trait TypeBoundsImpl {
    def sym: Symbol
    def inTpl: TemplateImpl
    def lo = sym.info.bounds match {
      case TypeBounds(lo, hi) if lo.typeSymbol != NothingClass =>
        Some(makeTypeInTemplateContext(appliedType(lo, sym.info.typeParams map {_.tpe}), inTpl, sym))
      case _ => None
    }
    def hi = sym.info.bounds match {
      case TypeBounds(lo, hi) if hi.typeSymbol != AnyClass =>
        Some(makeTypeInTemplateContext(appliedType(hi, sym.info.typeParams map {_.tpe}), inTpl, sym))
      case _ => None
    }
  }

  trait HigherKindedImpl extends HigherKinded {
    def sym: Symbol
    def inTpl: TemplateImpl
    def typeParams =
      sym.typeParams map (makeTypeParam(_, inTpl))
  }
  /* ============== MAKER METHODS ============== */

  /** This method makes it easier to work with the different kinds of symbols created by scalac by stripping down the
   * package object abstraction and placing members directly in the package.
   *
   * Here's the explanation of what we do. The code:
   * {{{
   * package foo {
   *   object `package` {
   *     class Bar
   *   }
   * }
   * }}}
   * will yield this Symbol structure:
   * <pre>
   *                                       +---------+ (2)
   *                                       |         |
   * +---------------+         +---------- v ------- | ---+                              +--------+ (2)
   * | package foo#1 <---(1)---- module class foo#2  |    |                              |        |
   * +---------------+         | +------------------ | -+ |         +------------------- v ---+   |
   *                           | | package object foo#3 <-----(1)---- module class package#4  |   |
   *                           | +----------------------+ |         | +---------------------+ |   |
   *                           +--------------------------+         | | class package\$Bar#5 | |   |
   *                                                                | +----------------- | -+ |   |
   *                                                                +------------------- | ---+   |
   *                                                                                     |        |
   *                                                                                     +--------+
   * </pre>
   * (1) sourceModule
   * (2) you get out of owners with .owner
   *
   * and normalizeTemplate(Bar.owner) will get us the package, instead of the module class of the package object.
   */
  def normalizeTemplate(aSym: Symbol): Symbol = aSym match {
    case null | rootMirror.EmptyPackage | NoSymbol =>
      normalizeTemplate(RootPackage)
    case ObjectClass =>
      normalizeTemplate(AnyRefClass)
    case _ if aSym.isPackageObject =>
      normalizeTemplate(aSym.owner)
    case _ if aSym.isModuleClass =>
      normalizeTemplate(aSym.sourceModule)
    case _ =>
      aSym
  }

  /**
   * These are all model construction methods. Please do not use them directly, they are calling each other recursively
   * starting from makeModel. On the other hand, makeTemplate, makeAnnotation, makeMember, makeType should only be used
   * after the model was created (modelFinished=true) otherwise assertions will start failing.
   */
  object modelCreation {

    def createRootPackage: PackageImpl = docTemplatesCache.get(RootPackage) match {
      case Some(root: PackageImpl) => root
      case _ => modelCreation.createTemplate(RootPackage, null) match {
        case Some(root: PackageImpl) => root
        case _ => throw new IllegalStateException("Scaladoc: Unable to create root package!")
      }
    }

    /**
     *  Create a template, either a package, class, trait or object
     */
    def createTemplate(aSym: Symbol, inTpl: DocTemplateImpl): Option[MemberImpl] = {
      // don't call this after the model finished!
      assert(!modelFinished, (aSym, inTpl))

      def createRootPackageComment: Option[Comment] =
        if(settings.docRootContent.isDefault) None
        else {
          import Streamable._
          Path(settings.docRootContent.value) match {
            case f : File => {
              val rootComment = closing(f.inputStream())(is => parse(slurp(is), "", NoPosition, inTpl))
              Some(rootComment)
            }
            case _ => None
          }
        }

      def createDocTemplate(bSym: Symbol, inTpl: DocTemplateImpl): DocTemplateImpl = {
        assert(!modelFinished, (bSym, inTpl)) // only created BEFORE the model is finished
        if (bSym.isAliasType && bSym != AnyRefClass)
          new DocTemplateImpl(bSym, inTpl) with AliasImpl with AliasType { override def isAliasType = true }
        else if (bSym.isAbstractType)
          new DocTemplateImpl(bSym, inTpl) with TypeBoundsImpl with AbstractType { override def isAbstractType = true }
        else if (bSym.isModule)
          new DocTemplateImpl(bSym, inTpl) with Object {}
        else if (bSym.isTrait)
          new DocTemplateImpl(bSym, inTpl) with Trait {}
        else if (bSym.isClass && bSym.asClass.baseClasses.contains(AnnotationClass))
          new DocTemplateImpl(bSym, inTpl) with model.AnnotationClass {}
        else if (bSym.isClass || bSym == AnyRefClass)
          new DocTemplateImpl(bSym, inTpl) with Class {}
        else
          throw new IllegalArgumentException(s"'$bSym' isn't a class, trait or object thus cannot be built as a documentable template.")
      }

      val bSym = normalizeTemplate(aSym)
      if (docTemplatesCache isDefinedAt bSym)
        return Some(docTemplatesCache(bSym))

      /* Three cases of templates:
       * (1) root package -- special cased for bootstrapping
       * (2) package
       * (3) class/object/trait
       */
      if (bSym == RootPackage) // (1)
        Some(new RootPackageImpl(bSym) {
          override lazy val comment = createRootPackageComment
          override val name = "root"
          override def inTemplate = this
          override def toRoot = this :: Nil
          override def qualifiedName = "_root_"
          override def isRootPackage = true
          override lazy val memberSyms =
            (bSym.info.members ++ EmptyPackage.info.members).toList filter { s =>
              s != EmptyPackage && s != RootPackage
            }
        })
      else if (bSym.hasPackageFlag) // (2)
        if (settings.skipPackage(makeQualifiedName(bSym)))
          None
        else
          inTpl match {
            case inPkg: PackageImpl =>
              val pack = new PackageImpl(bSym, inPkg) {}
              // Used to check package pruning works:
              //println(pack.qualifiedName)
              if (pack.templates.filter(_.isDocTemplate).isEmpty && pack.memberSymsLazy.isEmpty) {
                droppedPackages += pack
                None
              } else
                Some(pack)
            case _ =>
              throw new IllegalArgumentException(s"'$bSym' must be in a package")
          }
      else {
        // no class inheritance at this point
        assert(inOriginalOwner(bSym, inTpl), s"$bSym in $inTpl")
        Some(createDocTemplate(bSym, inTpl))
      }
    }

    /**
     *  After the model is completed, no more DocTemplateEntities are created.
     *  Therefore any symbol that still appears is:
     *   - MemberTemplateEntity (created here)
     *   - NoDocTemplateEntity (created in makeTemplate)
     */
    def createLazyTemplateMember(aSym: Symbol, inTpl: DocTemplateImpl): MemberImpl = {

      // Code is duplicate because the anonymous classes are created statically
      def createNoDocMemberTemplate(bSym: Symbol, inTpl: DocTemplateImpl): MemberTemplateImpl = {
        assert(modelFinished, "cannot create NoDocMember template before model is finished") // only created AFTER the model is finished
        if (bSym.isModule || (bSym.isAliasType && bSym.tpe.typeSymbol.isModule))
          new MemberTemplateImpl(bSym, inTpl) with Object {}
        else if (bSym.isTrait || (bSym.isAliasType && bSym.tpe.typeSymbol.isTrait))
          new MemberTemplateImpl(bSym, inTpl) with Trait {}
        else if (bSym.isClass || (bSym.isAliasType && bSym.tpe.typeSymbol.isClass))
          new MemberTemplateImpl(bSym, inTpl) with Class {}
        else
          throw new IllegalArgumentException(s"'$bSym' isn't a class, trait or object thus cannot be built as a member template.")
      }

      assert(modelFinished, "cannot create lazy template member before model is finished")
      val bSym = normalizeTemplate(aSym)

      if (docTemplatesCache isDefinedAt bSym)
        docTemplatesCache(bSym)
      else
        docTemplatesCache.get(bSym.owner) match {
          case Some(docTpl) =>
            docTpl.members.collect { case mbr: MemberImpl if mbr.sym == bSym => mbr } match {
              case h :: Nil => h
              case _ => throw new AssertionError("must have exactly one member with bSym")
            }
          case _ =>
            // move the class completely to the new location
            createNoDocMemberTemplate(bSym, inTpl)
        }
    }
  }

  // TODO: Should be able to override the type
  def makeMember(aSym: Symbol, conversion: Option[ImplicitConversionImpl], inTpl: DocTemplateImpl): List[MemberImpl] = {

    def makeMember0(bSym: Symbol, useCaseOf: Option[MemberImpl]): Option[MemberImpl] = {
      if (bSym.isGetter && bSym.isLazy)
          Some(new NonTemplateMemberImpl(bSym, conversion, useCaseOf, inTpl) with Val {
            override def isLazyVal = true
          })
      else if (bSym.isGetter && bSym.accessed.isMutable)
        Some(new NonTemplateMemberImpl(bSym, conversion, useCaseOf, inTpl) with Val {
          override def isVar = true
        })
      else if (bSym.isMethod && !bSym.hasAccessorFlag && !bSym.isConstructor && !bSym.isModule) {
        val cSym: Symbol =
          if (bSym == definitions.Object_synchronized) modifiedSynchronized
          else bSym
        Some(new NonTemplateParamMemberImpl(cSym, conversion, useCaseOf, inTpl) with HigherKindedImpl with Def {
          override def isDef = true
        })
      }
      else if (bSym.isConstructor)
        if (conversion.isDefined || (bSym.enclClass.isAbstract && (bSym.enclClass.isSealed || bSym.enclClass.isFinal)))
          // don't list constructors inherited by implicit conversion
          // and don't list constructors of abstract sealed types (they cannot be accessed anyway)
          None
        else
          Some(new NonTemplateParamMemberImpl(bSym, conversion, useCaseOf, inTpl) with Constructor {
            override def isConstructor = true
            def isPrimary = sym.isPrimaryConstructor
          })
      else if (bSym.isGetter) // Scala field accessor or Java field
        Some(new NonTemplateMemberImpl(bSym, conversion, useCaseOf, inTpl) with Val {
          override def isVal = true
        })
      else if (bSym.isAbstractType && !typeShouldDocument(bSym, inTpl))
        Some(new MemberTemplateImpl(bSym, inTpl) with TypeBoundsImpl with AbstractType {
          override def isAbstractType = true
        })
      else if (bSym.isAliasType && !typeShouldDocument(bSym, inTpl))
        Some(new MemberTemplateImpl(bSym, inTpl) with AliasImpl with AliasType {
          override def isAliasType = true
        })
      else if (!modelFinished && (bSym.hasPackageFlag || templateShouldDocument(bSym, inTpl)))
        modelCreation.createTemplate(bSym, inTpl)
      else
        None
    }

    if (!localShouldDocument(aSym) || aSym.isModuleClass || aSym.isPackageObject || aSym.isMixinConstructor)
      Nil
    else {
      val allSyms = useCases(aSym, inTpl.sym) map { case (bSym, bComment, bPos) =>
        docComments.put(bSym, DocComment(bComment, bPos)) // put the comment in the list, don't parse it yet, closes scala/bug#4898
        bSym
      }

      val member = makeMember0(aSym, None)
      if (allSyms.isEmpty)
        member.toList
      else
        // Use cases replace the original definitions - scala/bug#5054
        allSyms flatMap { makeMember0(_, member) }
    }
  }

  def findMember(aSym: Symbol, inTpl: DocTemplateImpl): Option[MemberImpl] = {
    normalizeTemplate(aSym.owner)
    inTpl.members.find(_.sym == aSym)
  }

  def findTemplateMaybe(aSym: Symbol): Option[DocTemplateImpl] = {
    assert(modelFinished, "cannot try to find template before model is finished")
    docTemplatesCache.get(normalizeTemplate(aSym)).filterNot(packageDropped(_))
  }

  def makeTemplate(aSym: Symbol): TemplateImpl = makeTemplate(aSym, None)

  def makeTemplate(aSym: Symbol, inTpl: Option[TemplateImpl]): TemplateImpl = {
    assert(modelFinished, "cannot make template before model is finished")

    def makeNoDocTemplate(aSym: Symbol, inTpl: TemplateImpl): NoDocTemplateImpl =
      noDocTemplatesCache.getOrElse(aSym, new NoDocTemplateImpl(aSym, inTpl))

    findTemplateMaybe(aSym) getOrElse {
      val bSym = normalizeTemplate(aSym)
      makeNoDocTemplate(bSym, inTpl getOrElse makeTemplate(bSym.owner))
    }
  }

  def makeAnnotation(annot: AnnotationInfo): scala.tools.nsc.doc.model.Annotation = {
    val aSym = annot.symbol
    new EntityImpl(aSym, makeTemplate(aSym.owner)) with scala.tools.nsc.doc.model.Annotation {
      lazy val annotationClass =
        makeTemplate(annot.symbol)
      val arguments = {
        val paramsOpt: Option[List[ValueParam]] = annotationClass match {
          case aClass: DocTemplateEntity with Class =>
            val constr = aClass.constructors collectFirst {
              case c: MemberImpl if c.sym == annot.original.symbol => c
            }
            constr flatMap (_.valueParams.headOption)
          case _ => None
        }
        val argTrees = annot.args map makeTree
        paramsOpt match {
          case Some (params) =>
            params zip argTrees map { case (param, tree) =>
              new ValueArgument {
                def parameter = Some(param)
                def value = tree
              }
            }
          case None =>
            argTrees map { tree =>
              new ValueArgument {
                def parameter = None
                def value = tree
              }
            }
        }
      }
    }
  }

  /** */
  def makeTypeParam(aSym: Symbol, inTpl: TemplateImpl): TypeParam =
    new ParameterImpl(aSym, inTpl) with TypeBoundsImpl with HigherKindedImpl with TypeParam {
      def variance: String = {
        if (sym hasFlag Flags.COVARIANT) "+"
        else if (sym hasFlag Flags.CONTRAVARIANT) "-"
        else ""
      }
    }

  /** */
  def makeValueParam(aSym: Symbol, inTpl: DocTemplateImpl): ValueParam = {
    makeValueParam(aSym, inTpl, aSym.nameString)
  }


  /** */
  def makeValueParam(aSym: Symbol, inTpl: DocTemplateImpl, newName: String): ValueParam =
    new ParameterImpl(aSym, inTpl) with ValueParam {
      override val name = newName
      def defaultValue =
        if (aSym.hasDefault) {
          val sourceFile = aSym.sourceFile
          // units.filter should return only one element
          (currentRun.units filter (_.source.file == sourceFile)).toList match {
            case List(unit) =>
              // scala/bug#4922 `sym == aSym` is insufficient if `aSym` is a clone of symbol
              //         of the parameter in the tree, as can happen with type parameterized methods.
              def isCorrespondingParam(sym: Symbol) = (
                sym != null &&
                sym != NoSymbol &&
                sym.owner == aSym.owner &&
                sym.name == aSym.name &&
                sym.isParamWithDefault
              )
              unit.body find (t => isCorrespondingParam(t.symbol)) collect {
                case ValDef(_,_,_,rhs) if rhs ne EmptyTree  => makeTree(rhs)
              }
            case _ => None
          }
        }
        else None
      def resultType =
        makeTypeInTemplateContext(aSym.tpe, this.inTpl, aSym)
      def isImplicit = aSym.isImplicit
    }

  /** */
  def makeTypeInTemplateContext(aType: Type, inTpl: TemplateImpl, dclSym: Symbol): TypeEntity = {
    val tpe = {
      def ownerTpl(sym: Symbol): Symbol =
        if (sym.isClass || sym.isModule || sym == NoSymbol) sym else ownerTpl(sym.owner)
      val fixedSym = if (inTpl.sym.isModule) inTpl.sym.moduleClass else inTpl.sym
      aType.asSeenFrom(fixedSym.thisType, ownerTpl(dclSym))
    }
    makeType(tpe, inTpl)
  }

  /** Get the types of the parents of the current class, ignoring the refinements */
  def makeParentTypes(aType: Type, tpl: Option[MemberTemplateImpl], inTpl: TemplateImpl): List[(TemplateEntity, TypeEntity)] = aType match {
    case RefinedType(parents, defs) =>
      val ignoreParents = Set[Symbol](AnyClass, AnyRefClass, ObjectClass)
      val filtParents =
        // we don't want to expose too many links to AnyRef, that will just be redundant information
        tpl match {
          case Some(tpl) if (!tpl.sym.isModule && parents.length < 2) || (tpl.sym == AnyValClass) || (tpl.sym == AnyRefClass) || (tpl.sym == AnyClass) => parents
          case _ => parents.filterNot((p: Type) => ignoreParents(p.typeSymbol))
        }

      /** Returns:
       *   - a DocTemplate if the type's symbol is documented
       *   - a NoDocTemplateMember if the type's symbol is not documented in its parent but in another template
       *   - a NoDocTemplate if the type's symbol is not documented at all */
      def makeTemplateOrMemberTemplate(parent: Type): TemplateImpl = {
        def noDocTemplate = makeTemplate(parent.typeSymbol)
        findTemplateMaybe(parent.typeSymbol).getOrElse {
          parent match {
            case TypeRef(pre, sym, args) =>
              findTemplateMaybe(pre.typeSymbol)
                .flatMap(findMember(parent.typeSymbol, _).collect { case t: TemplateImpl => t })
                .getOrElse(noDocTemplate)
            case _ => noDocTemplate
          }
        }
      }

      filtParents.map(parent => {
        val templateEntity = makeTemplateOrMemberTemplate(parent)
        val typeEntity = makeType(parent, inTpl)
        (templateEntity, typeEntity)
      })
    case _ =>
      List((makeTemplate(aType.typeSymbol), makeType(aType, inTpl)))
  }

  def makeQualifiedName(sym: Symbol, relativeTo: Option[Symbol] = None): String = {
    val stop = relativeTo map (_.ownerChain.toSet) getOrElse Set[Symbol]()
    var sym1 = sym
    val path = new StringBuilder()
    // var path = List[Symbol]()

    while ((sym1 != NoSymbol) && (path.isEmpty || !stop(sym1))) {
      val sym1Norm = normalizeTemplate(sym1)
      if (!sym1.sourceModule.isPackageObject && sym1Norm != RootPackage) {
        if (path.nonEmpty)
          path.insert(0, ".")
        path.insert(0, sym1Norm.nameString)
        // path::= sym1Norm
      }
      sym1 = sym1.owner
    }

    optimize(path.toString)
    //path.mkString(".")
  }

  def inOriginalOwner(aSym: Symbol, inTpl: TemplateImpl): Boolean =
    normalizeTemplate(aSym.owner) == normalizeTemplate(inTpl.sym)

  def templateShouldDocument(aSym: Symbol, inTpl: DocTemplateImpl): Boolean =
    (aSym.isTrait || aSym.isClass || aSym.isModule || typeShouldDocument(aSym, inTpl)) &&
    localShouldDocument(aSym) &&
    !isEmptyJavaObject(aSym) &&
    // either it's inside the original owner or we can document it later:
    (!inOriginalOwner(aSym, inTpl) || (aSym.isPackageClass || (aSym.sourceFile != null)))

  def membersShouldDocument(sym: Symbol, inTpl: TemplateImpl) = {
    // pruning modules that shouldn't be documented
    // Why Symbol.isInitialized? Well, because we need to avoid exploring all the space available to scaladoc
    // from the classpath -- scaladoc is a hog, it will explore everything starting from the root package unless we
    // somehow prune the tree. And isInitialized is a good heuristic for pruning -- if the package was not explored
    // during typer and refchecks, it's not necessary for the current application and there's no need to explore it.
    (!sym.isModule || sym.moduleClass.isInitialized) &&
    // documenting only public and protected members
    localShouldDocument(sym) &&
    // Only this class's constructors are part of its members; inherited constructors are not.
    (!sym.isConstructor || sym.owner == inTpl.sym)
  }

  def isEmptyJavaObject(aSym: Symbol): Boolean =
    aSym.isModule && aSym.isJavaDefined &&
    aSym.info.members.exists(s => localShouldDocument(s) && (!s.isConstructor || s.owner == aSym))

  def localShouldDocument(aSym: Symbol): Boolean = {
    // For `private[X]`, isPrivate is false (while for protected[X], isProtected is true)
    def isPrivate = aSym.isPrivate || !aSym.isProtected && aSym.privateWithin != NoSymbol
    // for private, only document if enabled in settings and not top-level
    !aSym.isSynthetic && (!isPrivate || settings.visibilityPrivate.value && !aSym.isTopLevel)
  }

  // the implicit conversions that are excluded from the pages should not appear in the diagram
  def implicitExcluded(convertorMethod: String): Boolean = settings.hiddenImplicits(convertorMethod)

  // whether or not to create a page for an {abstract,alias} type
  def typeShouldDocument(bSym: Symbol, inTpl: DocTemplateImpl) =
    (settings.docExpandAllTypes.value && (bSym.sourceFile != null)) ||
    (bSym.isAliasType || bSym.isAbstractType) &&
    { val rawComment = global.expandedDocComment(bSym, inTpl.sym)
      rawComment.contains("@template") || rawComment.contains("@documentable") }
}
object ModelFactory {
  // Defaults for member grouping, that may be overridden by the template
  val defaultGroup = "Ungrouped"
  val defaultGroupName = "Ungrouped"
  val defaultGroupDesc = None
  val defaultGroupPriority = 1000

  val tokens = raw"€\{($FILE_PATH|$FILE_EXT|$FILE_PATH_EXT|$FILE_LINE|$TPL_OWNER|$TPL_NAME)\}".r
  final val FILE_PATH     = "FILE_PATH"
  final val FILE_EXT      = "FILE_EXT"
  final val FILE_PATH_EXT = "FILE_PATH_EXT"
  final val FILE_LINE     = "FILE_LINE"
  final val TPL_OWNER     = "TPL_OWNER"
  final val TPL_NAME      = "TPL_NAME"

  val WordChar = raw"(\w)".r

  def expandUrl(urlTemplate: String, filePath: String, fileExt: String, filePathExt: String, line: Int, tplOwner: String, tplName: String): String = {
    val absolute = filePath.startsWith("/")

    def subst(token: String, index: Int): String = {
      // If a relative path follows a word character, insert a `/`
      def sep: String =
        if (index > 0 && !absolute && WordChar.matches(urlTemplate.substring(index-1, index))) "/"
        else ""
      def dotted: Boolean = index > 0 && urlTemplate(index-1) == '.'

      token match {
        case FILE_PATH     => s"$sep$filePath"
        case FILE_EXT      => if (dotted) fileExt.stripPrefix(".") else fileExt
        case FILE_PATH_EXT => s"$sep$filePathExt"
        case FILE_LINE     => line.toString
        case TPL_OWNER     => tplOwner
        case TPL_NAME      => tplName
      }
    }
    tokens.replaceAllIn(urlTemplate, m => quoteReplacement(subst(m.group(1), m.start)))
  }
}
